#include "KStorage.h"
#include "KDebug.h"
#include "KPageList.h"
#include "KPageMemory.h"
#include <algorithm>

KStorage::KStorage(KPageList *pool) : m_pool(pool), m_bytes(0)
{
}

KStorage::~KStorage()
{
    clear();
    free();
}

void KStorage::clear()
{
    m_bytes = 0;
    shift(m_used, m_free, false);
}

void KStorage::free()
{
    if (m_pool) {
        shift(m_free, *m_pool);
    } else {
        m_free.destroy();
    }
}

bool KStorage::ignore(size_t length)
{
    if (length <= bytesAvailable()) {
        if (length > 0) {
            InterlockedExchangeAdd(&m_bytes, -LONG(length));
            for (;;) {
                length -= static_cast<KPage *>(m_used.front())->ignore(length);
                if (length > 0) {
                    m_free.pushFront(m_used.popFront());
                } else {
                    break;
                }
            }
        }
        return true;
    }
    return false;
}

bool KStorage::peek(void *dest, size_t length) const
{
    assert(dest && length > 0);
    if (length <= bytesAvailable()) {
        size_t n;
        uint8_t *ptr = static_cast<uint8_t *>(dest);
        for (KList::NodePtr node = m_used.front(); ; node = node->next()) {
            n = static_cast<KPage *>(node)->peek(ptr, length);
            length -= n;
            ptr += n;
            if (!length) {
                break;
            }
        }
        return true;
    }
    return false;
}

bool KStorage::read(void *dest, size_t length)
{
    assert(dest && length > 0);
    if (length <= bytesAvailable()) {
        InterlockedExchangeAdd(&m_bytes, -LONG(length));
        size_t n;
        uint8_t *ptr = static_cast<uint8_t *>(dest);
        for (;;) {
            n = static_cast<KPage *>(m_used.front())->read(ptr, length);
            length -= n;
            ptr += n;
            if (length > 0) {
                m_free.pushFront(m_used.popFront());
            } else {
                break;
            }
        }
        return true;
    }
    return false;
}

bool KStorage::write(const void *src, size_t length)
{
    if (src && length > 0) {
        KPage *page;
        size_t n;
        const uint8_t *ptr = static_cast<const uint8_t *>(src);
        for (;;) {
            if ((page = getPageToWrite())) {
                n = page->write(ptr, length);
                length -= n;
                ptr += n;
                InterlockedExchangeAdd(&m_bytes, LONG(n));
                if (!length) {
                    break;
                }
            } else {
                return false;
            }
        }
    }
    return true;
}

size_t KStorage::write(KStorage *src, size_t length)
{
    assert(src != this);
    size_t written = 0;
    if (src && (length = std::min<size_t>(length, src->bytesAvailable()))) {
        KPage *page;
        size_t n;
        for (;;) {
            if ((page = getPageToWrite())) {
                n = page->write(src, length);
                length -= n;
                written += n;
                InterlockedExchangeAdd(&m_bytes, LONG(n));
                if (length > 0) {
                    continue;
                }
            }
            break;
        }
    }
    return written;
}

KPage *KStorage::createPage()
{
    return new KPageMemory(m_pool ? m_pool->pageSize() : 0);
}

KPage *KStorage::getPageToWrite()
{
#define RESET_PAGE(page)                                    \
    do {                                                    \
        if (page->reset()) {                                \
            m_used.pushBack(page);                          \
            return page;                                    \
        } else {                                            \
            KDebugMsg("Warning: KPage failed to reset");    \
            delete page;                                    \
        }                                                   \
    } while (false)
    KPage *page;
    if ((page = static_cast<KPage *>(m_used.back()))) {
        if (!page->atEnd()) {
            return page;
        }
    }
    while ((page = static_cast<KPage *>(m_free.popFront()))) {
        RESET_PAGE(page);
    }
    if (m_pool) {
        while ((page = static_cast<KPage *>(m_pool->popFront()))) {
            RESET_PAGE(page);
        }
    }
    if ((page = createPage())) {
        RESET_PAGE(page);
    } else {
        KDebugMsg("Warning: KPage failed to create");
    }
    return NULL;
#undef RESET_PAGE
}

void KStorage::shift(KList &from, KList &to, bool locked)
{
    if (locked) {
        while (!from.isEmpty()) {
            to.pushFront(from.popFrontUnlocked());
        }
    } else {
        while (!from.isEmpty()) {
            to.pushFrontUnlocked(from.popFrontUnlocked());
        }
    }
}